#ifndef DATA_SCENE_H_
#define DATA_SCENE_H_
#include <filesystem>
#include <list>
#include <memory>
#include <opencv2/core.hpp>
#include <vector>

#include "fixed_array.h"

namespace autodrive {
namespace perception {

class DataSceneImpl;

class DataScene {
 public:
  /// @brief max lidar number
  static constexpr std::uint32_t kMaxLidarNum{8};
  /// @brief max camera number
  static constexpr std::uint32_t kMaxCameraNum{16};
  /// @brief max radar number
  static constexpr std::uint32_t kMaxRadarNum{16};
  ///@brief max point cloud number
  static constexpr std::uint32_t kMaxPointNum{400000U};
  ///@brief max matrix data size
  static constexpr std::uint32_t kMaxMatrixDataSize{16U};
  /// @brief max dataframe number
  static constexpr std::uint32_t kMaxDataFrameNumber{81920};
  /// @brief max calib data number
  static constexpr std::uint32_t kMaxCalibDataNum{64};

  /// @brief data frame array
  /// @tparam T type
  template <typename T>
  using DataFrameArray = fixed_array<T, kMaxDataFrameNumber>;
  /// @brief dataframe array pointer
  /// @tparam T type
  template <typename T>
  using DataFrameArrayPtr = std::shared_ptr<DataFrameArray<T>>;

  /// @brief
  /// @tparam T
  template <typename T>
  using CalibDataArray = fixed_array<T, kMaxCalibDataNum>;
  /// @brief
  /// @tparam T
  template <typename T>
  using CalibDataArrayPtr = std::shared_ptr<CalibDataArray<T>>;

 public:
  struct MetaDataInfo {
    /// @brief name
    std::string name;
    /// @brief description
    std::string description;
    /// @brief tags
    std::list<std::string> tags;
    /// @brief lidar_channels
    std::vector<std::string> lidar_channels;
    /// @brief radar_channels
    std::vector<std::string> radar_channels;
    /// @brief main_sensor
    std::string main_sensor;
    /// @brief weather
    std::string weather;
    /// @brief lighting
    std::string lighting;
    /// @brief road_condition
    std::string road_condition;
    /// @brief road_type
    std::string road_type;
  };

  /// @brief data base
  struct DataBase {
    /// @brief name
    std::string name;
    /// @brief timestamp
    std::uint64_t timestamp;
    /// @brief is valid
    bool is_valid;
  };

  // Point XYZIT
  struct PointXYZIT {
    ///@brief X
    float x;
    ///@brief Y
    float y;
    ///@brief Z
    float z;
    ///@brief Intensity
    float intensity;
    ///@brief Time stamp
    double timestamp;
    ///@brief Ring number
    std::uint16_t ring;
  };

  // Point Doppler
  struct PointXTZDoppler {
    ///@brief X
    float x;
    ///@brief Y
    float y;
    ///@brief Z
    float z;
    ///@brief vx
    float vx;
    ///@brief vy
    float vy;
    ///@brief rcs
    float rcs;
    ///@brief Time stamp
    std::uint64_t timestamp;
  };

  using LidarPointCloud = fixed_array<PointXYZIT, kMaxPointNum>;

  struct LidarData : DataBase {
    /// @brief points
    LidarPointCloud point_cloud;
  };

  struct CameraData : DataBase {
    /// @brief image
    cv::Mat image;
  };

  struct RadarData : DataBase {
    /// @brief points
    fixed_array<PointXTZDoppler, kMaxPointNum> points;
  };

  struct MatrixData : DataBase {
    fixed_array<double, kMaxMatrixDataSize> raw_data;
  };

  using LidarPoindCloud = fixed_array<PointXYZIT, kMaxPointNum>;
  using LidarDataArray = fixed_array<LidarData, kMaxLidarNum>;
  using CameraDataArray = fixed_array<CameraData, kMaxCameraNum>;
  using radarDataArray = fixed_array<RadarData, kMaxRadarNum>;

  /// @brief
  struct alignas(8) DataFrame {
    /// @brief timestamp
    std::uint64_t timestamp;
    /// @brief lidar data
    LidarDataArray lidar_data;
    /// @brief camera data
    CameraDataArray camera_data;
    /// @brief radar data
    radarDataArray radar_data;
    /// @brief ego data
    MatrixData ego_data;
  };

  /// @brief
  struct LoadDataFrameSettings {
    /// @brief load lidar data list, empty load all or load specified
    std::vector<std::string> lidars;
    /// @brief load camera data list, empty load all or load specified
    std::vector<std::string> cameras;
    /// @brief load radar data list, empty load all or load specified
    std::vector<std::string> radars;
  };

 public:
  DataScene();
  DataScene(DataScene &&) = delete;
  DataScene(const DataScene &) = delete;
  DataScene &operator=(DataScene &&) = delete;
  DataScene &operator=(const DataScene &) = delete;
  ~DataScene();

 public:
  /// @brief
  /// @param path
  void Init(std::filesystem::path const &path) noexcept(false);

  /// @brief
  /// @param dataFrameKey
  /// @param dataFrame
  void LoadDataFrame(LoadDataFrameSettings const &settings,
                     std::string const &dataFrameKey,
                     DataFrame &dataFrame) noexcept(false);

  /// @brief
  /// @return
  bool IsInit() const noexcept;

  /// @brief get dataframe keys
  /// @return dataframe key sequence list
  DataFrameArrayPtr<std::string> DataFrameKeys() const noexcept;

  /// @brief get calib data
  /// @return calib data list pointer
  CalibDataArrayPtr<MatrixData> CalibData() const noexcept;

  /// @brief get meta data
  /// @return scene meta data
  MetaDataInfo MetaData() const noexcept;

  /// @brief
  /// @return
  std::filesystem::path const &Path() const noexcept(false);

 private:
  /// @brief data scene implementation pointer
  std::shared_ptr<DataSceneImpl> data_scene_impl_ptr_;
};

}  // namespace perception
}  // namespace autodrive

#endif